package in

import (
	"encoding/json"
	"errors"
	"fmt"
	"log"
	"strconv"
	"strings"
)

/**
*
this language include 3 area

1. SELECTOR AREA   |   2.  GRAPHY OPER AREA     | 3.  SELECTED DATA  OPTIONS OPER AREA

1. SELCTOR AREA

 - include two area

 SET kind :
    all / some
 QUERY kind:
 [key]: [value]                                                   (type 1)
 [key world]                                                      (type 2)

 2. GRAPHY OPER

  - include two oper

  connect
  similar
  trace


  3. DATA OPTIONS

  1. limit  	[10000]
  2. over db


Default Setting :

example :
query id == 1232 and include  some fuzzy key , then trace who data's parent_id -> these data's id, limit 1000.

'some id:1232, fuzzy key ||  TRACE parent_id : id || LIMIT 1000 '

query id || show nodes

'some id: 356'

all data connected by some id:
	' CONNECT id '

if no ALL / SOME :
 will set 'ALL'



**/

const (
	SELECT_ALL  = 0x000000
	SELECT_SOME = 0x001000

	SELECT_ALL_STR  = "ALL"
	SELECT_SOME_STR = "SOME"

	GRAPH_CONN      = 0x010000
	GRAPH_SIM       = 0x100000
	GRAPH_TRACE     = 0x110000
	GRAPH_CONN_STR  = "CONNECT"
	GRAPH_SIM_STR   = "SIM"
	GRAPH_TRACE_STR = "TRACE"

	DATA_FILTER     = 0x000001
	DATA_FILTER_STR = "LIMIT"
)

type LanguageOper struct {
	SELCTOR      int
	GRAPHYRENDER int
	DATA_DEAL    int

	Dataset   string
	Layout    string
	FilterKey string
	QUERY     map[string]string
	RENDERKEY map[string]string
	OPTIONS   map[string]int
}

func CompileGQL(line string) (lp *LanguageOper, err error) {

	lp = new(LanguageOper)
	lp.QUERY = make(map[string]string)
	lp.RENDERKEY = make(map[string]string)

	lineFields := strings.Split(line, "||")
	switch len(lineFields) {
	case 1:
		if strings.HasPrefix(strings.ToUpper(line), "SOME") {

			lp.SELCTOR = SELECT_SOME
			err = parseQuery(lp, line)
			if err != nil {
				return
			}
		} else {
			lp.SELCTOR = SELECT_ALL
			parseRenderWay(lp, lineFields[0])

		}

	case 2:
		if strings.HasPrefix(strings.ToUpper(line), "SOME") {
			lp.SELCTOR = SELECT_SOME
			err = parseQuery(lp, lineFields[0])
			if err != nil {
				return
			}
			err = parseRenderWay(lp, lineFields[1])
		} else {
			lp.SELCTOR = SELECT_ALL
			parseRenderWay(lp, lineFields[0])
			parseOptions(lp, lineFields[1])

		}
	case 3:
		lp.SELCTOR = SELECT_SOME
		parseQuery(lp, lineFields[0])
		parseQuery(lp, lineFields[1])
		parseOptions(lp, lineFields[2])
	}
	// if strings.HasPrefix(strings.ToUpper(lineFieds[0]), "SOME") || strings.HasPrefix(strings.ToUpper(lineFieds[0]), "ALL") {

	// } else {

	// }
	// parsedNo := 0
	// switch strings.ToUpper(lineFields[0]) {
	// case SELECT_SOME_STR:
	// 	lp.SELCTOR = SELECT_SOME
	// 	parsedNo = 1
	// case SELECT_ALL_STR:
	// 	lp.SELCTOR = SELECT_ALL
	// 	parsedNo = 1
	// default:
	// 	lp.SELCTOR = SELECT_ALL
	// 	parsedNo = 0
	// }

	return
}

func parseRenderWay(lp *LanguageOper, line string) (err error) {
	line = strings.TrimSpace(line)
	log.Println("render ->", line)
	words := strings.SplitN(line, " ", 2)
	other := strings.TrimSpace(words[1])
	log.Println("render oper ->", other)
	switch strings.ToUpper(words[0]) {
	case GRAPH_CONN_STR:
		lp.GRAPHYRENDER = GRAPH_CONN
	case GRAPH_SIM_STR:
		lp.GRAPHYRENDER = GRAPH_SIM
		return
	case GRAPH_TRACE_STR:
		lp.GRAPHYRENDER = GRAPH_TRACE
	default:
		return errors.New("Must have a graph oper !! ")
	}
	if strings.Contains(other, ":") {
		os := strings.SplitN(other, ":", 2)

		log.Println("render oper map : ", os[0], os[1])
		lp.RENDERKEY[strings.TrimSpace(os[0])] = strings.TrimSpace(os[1])
	} else {
		lp.RENDERKEY[other] = other
	}
	return
}

func parseQuery(lp *LanguageOper, line string) (err error) {
	// if strings.HasPrefix(line)
	log.Println("query ->", line)
	line = strings.TrimSpace(line)

	if line == "" {
		return errors.New("EMPTY QUERY  !")
	} else if !strings.HasPrefix(strings.ToUpper(line[:4]), "SOME") {
		return errors.New("QUERY MUST START AT SOME")
	}

	fields := SplitAtCommas(line[4:])
	for _, field := range fields {
		field = strings.TrimSpace(field)
		w := strings.SplitN(field, ":", 2)
		if len(w) == 2 {
			lp.QUERY[strings.TrimSpace(w[0])] = strings.TrimSpace(w[1])
		} else {
			lp.FilterKey = strings.TrimSpace(field)
		}
	}
	log.Println("query ->", lp.QUERY)
	log.Println("key ->", lp.FilterKey)
	return
}

func parseOptions(lp *LanguageOper, line string) (err error) {
	if strings.Contains(line, "ver") {
		lp.Layout = "veritcal"
		line = strings.ReplaceAll(line, "ver", "")
	}
	if strings.Contains(line, "data") {
		ls := strings.SplitN(line, "data", 2)
		lp.Dataset = strings.Fields(ls[1])[0]
	}
	return
}

// func parseQuery(lp *LanguageOper, line string) (err error){
// 	if lineFields[parsedNo] == '"'|| lineFields[parsedNo] == "'"{
// 		// from '" to scan  "some" : "value  "
// 		start_f := lineFields[parsedNo]
// 		opers := []string{}
// 		hasValue := false
// 		parsedNo ++
// 		for no,word := range lineFields[parsedNo:]{
// 			if word == start_f{
// 				if lineFields[no+1] == ":"{
// 					hasValue = true

// 				}
// 				parsedNo += no +1
// 				break
// 			}
// 			opers = append(opers, word)
// 		}
// 		key := strings.Join(opers, " ")
// 		opers = []string{}
// 		if hasValue{
// 			if lineFields[parsedNo] == "'" || lineFields[parsedNo] == '"'{
// 				start_f = lineFields[parsedNo]
// 				parsedNo++
// 				for no, word := range lineFields[parsedNo:]{
// 					if word == start_f{
// 						parsedNo += no +1
// 						break
// 					}
// 					opers = append(opers, word)
// 				}

// 			}

// 		}

// 	}else{

// 	}
// 	return
// }package in.

func SplitAtCommas(s string) []string {
	res := []string{}
	var beg int
	var inString bool

	for i := 0; i < len(s); i++ {
		if s[i] == ',' && !inString {
			res = append(res, strings.TrimSpace(s[beg:i]))
			beg = i + 1
		} else if s[i] == '\'' {
			if !inString {
				inString = true
			} else if i > 0 {
				inString = false
				if i > 0 && s[i-1] == '\\' {
					inString = true
				}
			}
		}
	}
	return append(res, s[beg:])
}

func (data Datas) OperWith(gql *LanguageOper) (out Datas) {
	var some Datas
	if gql.SELCTOR&SELECT_SOME == SELECT_SOME {

		var ifTestFuzzy bool
		if gql.FilterKey != "" {
			ifTestFuzzy = true
		}
		for _, da := range data {
			excludeThis := false
			if ifTestFuzzy {
				if testFuzzy, err := json.Marshal(da["data"]); err != nil {
					log.Fatal("lack [data]!")
				} else {
					if !strings.Contains(string(testFuzzy), gql.FilterKey) {
						excludeThis = true
					}
				}
			}
			if excludeThis {
				continue
			}
			if len(gql.QUERY) > 0 {
				for k, vstr := range gql.QUERY {
					vd, ok := da["data"]
					var v interface{}
					if ok {
						v, ok = vd.(G)[k]
					} else {
						v, ok = da[k]
					}
					// ColorL(da)
					if !ok {
						continue
					}
					tv := fmt.Sprint(v)
					if tv == vstr {
						some = append(some, da)
					}
				}
			} else {
				some = append(some, da)
			}

		}
	} else {
		for _, da := range data {
			some = append(some, da)
		}
	}
	ColorL("Filter Data:", len(some))
	switch gql.GRAPHYRENDER {
	case GRAPH_CONN:
		out = some.ConnectedWith(func(gf, gt G, same, connected bool) bool {
			ifConnected := false
			if !same {

				data1 := gf["data"].(G)
				data2 := gt["data"].(G)
				// ColorD(data1)
				// ColorD(data2)
				ifConnected = true
				for fromkey, tokey := range gql.RENDERKEY {
					if fmt.Sprint(data1[fromkey]) == fmt.Sprint(data2[tokey]) && data1[fromkey] != nil {
						// gt["label"] = tokey + ":" + fmt.Sprint(data2[tokey])
					} else {
						ifConnected = false
						// in.ColorD(gf)
						// in.ColorD(gt)
					}
				}
				return ifConnected
			}
			return ifConnected
		})
		return out
	case GRAPH_TRACE:
		if len(some) < len(data) {
			level := -1
			usedKeys := make(map[string]string)
			for rk, rv := range gql.RENDERKEY {
				if rk == rv {
					if l, err := strconv.Atoi(rk); err == nil {
						level = l
					} else {
						usedKeys[rk] = rv
					}
				} else {
					usedKeys[rk] = rv
				}
				// break
			}
			ColorL("Level:", level)
			ColorD(usedKeys)
			for _, data := range data {
				for _, data2 := range some {
					ifConn := false

					da := data["data"].(G)
					da2 := data2["data"].(G)
					fid := data["id"].(int)
					tid := data2["id"].(int)
					// Filter Same node
					if fid == tid {
						continue
					}
					/*
						use key to match
					*/
					if len(usedKeys) > 0 {
						ifConn = true
						for k, k2 := range usedKeys {
							if fmt.Sprint(da[k]) == fmt.Sprint(da2[k2]) && da[k] != nil && da2[k2] != nil {
								continue
							} else {
								ifConn = false
								break
							}
						}
					} else {
						/*
							use match number to match
						*/

						pointNum := 0
						for k, v := range da2 {
							if fid == tid {
								continue
							}
							if vf, ok := da[k]; ok && fmt.Sprint(vf) == fmt.Sprint(v) {
								if pointNum > level {
									ifConn = true
									break
								} else {
									pointNum++
								}
							}
						}
					}

					if ifConn {
						ColorD(da)
						ColorD(da2)
						out = append(out, G{
							"from":  fid,
							"to":    tid,
							"fromP": data,
							"toP":   data2,
						})
					}
				}
			}
			return out
		}

	default:
		return some
	}
	return
}
